/*
 * Copyright (C) 2000-2005 Philips Electronics N.V.
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or 
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file gnu_license.txt.  If not, write to
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  
 * 
 */
#include <gmp.h>
#include <front/mem.h>

#include "eval.h"
#include "errors.h"
#include "type.h"

static Bool do_adjust = TRUE;

/* ----------------------------------------------------------------------------
 * Minimum value of integral type T
 */
AP_Integer
type_min(Type t)
{
  Type u;

  if (is_enum_type (t)) u = int_type;
  else                  u = expanded_base_type (t);
  if (is_pointer_type (t)) return AP_Int (void_pointer_min);
  assert (is_integer_type (u));

  if (is_signed_type (t)) {
    if (is_long_int_type(t))  return AP_Int (signed_long_int_min);
    if (is_long_long_type(t)) return AP_Int (signed_long_long_min);
    if (u==char_type)         return AP_Int (signed_char_min);
    if (u==int_type)          return AP_Int (signed_int_min);
    if (u==short_type)        return AP_Int (signed_short_min);
  } else {
    if (is_long_int_type(t))  return AP_Int (unsigned_long_int_min);
    if (is_long_long_type(t)) return AP_Int (unsigned_long_long_min);
    if (u==char_type)         return AP_Int (unsigned_char_min);
    if (u==int_type)          return AP_Int (unsigned_int_min);
    if (u==short_type)        return AP_Int (unsigned_short_min);
  }
  abort();
}

/* ----------------------------------------------------------------------------
 * Maximum value of integral type T
 */
AP_Integer
type_max(Type t)
{
  Type u;

  if (is_enum_type (t)) u = int_type;
  else                  u = expanded_base_type (t);
  if (is_pointer_type (t)) return AP_Int (void_pointer_max);
  assert (is_integer_type (u));

  if (is_signed_type (t)) {
    if (is_long_int_type(t))  return AP_Int (signed_long_int_max);
    if (is_long_long_type(t)) return AP_Int (signed_long_long_max);
    if (u==char_type)         return AP_Int (signed_char_max);
    if (u==int_type)          return AP_Int (signed_int_max);
    if (u==short_type)        return AP_Int (signed_short_max);
  } else {
    if (is_long_int_type(t))  return AP_Int (unsigned_long_int_max);
    if (is_long_long_type(t)) return AP_Int (unsigned_long_long_max);
    if (u==char_type)         return AP_Int (unsigned_char_max);
    if (u==int_type)          return AP_Int (unsigned_int_max);
    if (u==short_type)        return AP_Int (unsigned_short_max);
  }
  abort();
}

/* ----------------------------------------------------------------------------
 * Wrap R if it is outside the range of T
 *   If T is not set, assume int
 */
static void
int_type_adjust(mpz_t r, Type t)
{
  Type u = t;
  if (!do_adjust) return;
  if (!u) u = int_type;
  AP_Integer min = type_min (u);
  AP_Integer max = type_max (u);

#warning Todo: do this more efficiently
  do {
    if (mpz_cmp (r, *max)>0) {
      mpz_sub (r, r, *max);
      mpz_sub_ui (r, r, 1);
      mpz_add (r, r, *min);
    } else if (mpz_cmp (r, *min)<0) {
      mpz_sub (r, r, *min);
      mpz_add_ui (r, r, 1);
      mpz_add (r, r, *max);
    } else break;
  } while(1);
}

Bool set_type_adjust (Bool adjust)
{
  Bool adj = do_adjust;
  do_adjust = adjust;
  return adj;
}

/* ----------------------------------------------------------------------------
 * Convert A to a integer type T
 */
static void
floating_cast(mpz_t r, mpf_t a, Type t)
{
  mpz_set_f (r, a);
  int_type_adjust (r, t);
}


/* ----------------------------------------------------------------------------
 * Evaluate LHS OP RHS where both operands are integers, and the
 * result type T is an integral type 
 */
static Bool
int_eval_bin (mpz_t r, operator op, mpz_t lhs, mpz_t rhs, Type t)
{
  switch (op) {
  case CorOp:  mpz_set_si (r, mpz_sgn(lhs) || mpz_sgn(rhs)); break;
  case CandOp: mpz_set_si (r, mpz_sgn(lhs) && mpz_sgn(rhs)); break;
  case OrOp:   mpz_ior (r, lhs, rhs); break;
  case XorOp:  mpz_xor (r, lhs, rhs); break;
  case AndOp:  mpz_and (r, lhs, rhs); break;
  case EqOp:   mpz_set_si (r, mpz_cmp(lhs,rhs) == 0); break;
  case NeOp:   mpz_set_si (r, mpz_cmp(lhs,rhs) != 0); break;
  case LtOp:   mpz_set_si (r, mpz_cmp(lhs,rhs) < 0);  break;
  case GtOp:   mpz_set_si (r, mpz_cmp(lhs,rhs) > 0);  break;
  case LeOp:   mpz_set_si (r, mpz_cmp(lhs,rhs) <= 0); break;
  case GeOp:   mpz_set_si (r, mpz_cmp(lhs,rhs) >= 0); break;
  case ShlOp:
    if (!mpz_fits_ulong_p (rhs)) return FALSE;
    mpz_mul_2exp (r, lhs, mpz_get_ui(rhs));
    break;
  case ShrOp:
    /* shift right of a negative value is implementation defined.  I
       think it's safe to assume that it's always an arithmetic shift
       right.
    */
    if (!mpz_fits_ulong_p (rhs)) return FALSE;
    mpz_fdiv_q_2exp (r, lhs, mpz_get_ui(rhs));
    break;
  case AddOp:  mpz_add (r, lhs, rhs); break;
  case SubOp:  mpz_sub (r, lhs, rhs); break;
  case MulOp:  mpz_mul (r, lhs, rhs); break;
  case DivOp:  
    if (!mpz_sgn(rhs)) return FALSE;
    if (divide_rounds_to_0)       mpz_tdiv_q (r, lhs, rhs);
    else /* divide rounds down */ mpz_fdiv_q (r, lhs, rhs);
    break;
  case ModOp:
    if (!mpz_sgn(rhs)) return FALSE;
    if (signed_modulo) mpz_tdiv_r (r, lhs, rhs);
    else               mpz_fdiv_r (r, lhs, rhs);
    break;
  default:
    return FALSE;
  }
  int_type_adjust (r, t);
  return TRUE;
}

/* ----------------------------------------------------------------------------
 * Evaluate LHS OP RHS where one of the operands is a pointer
 */
static Bool
ptr_eval_bin (mpz_t r, operator op, mpz_t lhs, Type lt, mpz_t rhs, Type rt)
{
  Bool ok;
  size_t elsize; 
  
  if (!is_pointer_type (lt)) return FALSE;
  elsize = sizeof_type (dereference_type (lt), &ok);
  if (!ok) return FALSE;
  switch (op) {
  case AddOp:
    if (is_pointer_type (rt)) return FALSE;
    mpz_mul_ui (rhs, rhs, elsize);
    mpz_add (r, lhs, rhs);
    break;
  case SubOp:
    if (!is_pointer_type (rt)) {
      mpz_mul_ui (rhs, rhs, elsize);
      mpz_sub (r, lhs, rhs);
    } else {
      if (!ptrs_to_compatible_types (lt, rt)) return FALSE;
      mpz_sub (r, lhs, rhs);
      if (signed_modulo) mpz_tdiv_r_ui (r, r, elsize);
      else               mpz_fdiv_r_ui (r, r, elsize);
    }
    break;
  default:
    return FALSE;
  }
  int_type_adjust (r, lt);
  return TRUE;
}

/* ----------------------------------------------------------------------------
 * Evaluate OP ARG where the operand is an integer, and the
 * result type T is an integral type
 */
static Bool
int_eval_mon (mpz_t r, monadic_operator op, mpz_t arg, Type t)
{
  switch (op) {
  case PosOp: mpz_set (r, arg); break;
  case NegOp: mpz_neg (r, arg); break;
  case InvOp: mpz_com (r, arg); break;
  case NotOp: mpz_set_si (r, mpz_sgn (arg)==0); break;
  default:
    return FALSE;
  }
  int_type_adjust (r, t);
  return TRUE;
}

/* ----------------------------------------------------------------------------
 *  Evaluate an integral constant expression (3.4)
 */
static Bool
eval_int_expr (mpz_t r, expression e)
{
  Bool v;
  mpz_t a, b;
  declarator d;
  Type t, t2;

  if (!e) return FALSE;
  
  switch (expression_tag(e)) {
  case bin_expression_kind:
    mpz_init (a);
    mpz_init (b);
    v =      eval_int_expr (a, bin_expression_lhs(e));
    v = v && eval_int_expr (b, bin_expression_rhs(e));
    if (is_pointer_type (expression_type (e))) {
      v = v && ptr_eval_bin (r, bin_expression_op(e),
                             a, expression_type(bin_expression_lhs(e)),
                             b, expression_type(bin_expression_rhs(e)));
    } else {
      v = v && int_eval_bin (r, bin_expression_op(e), a, b, expression_type(e));
    }
    return v;
  case mon_expression_kind: 
    mpz_init (a);
    v = eval_int_expr (a, mon_expression_arg(e));
    return v && int_eval_mon (r, mon_expression_op(e), a, expression_type(e));
  case CondExpr_kind:
    mpz_init (a);
    v = eval_int_expr (a, CondExpr_cond(e));
    if (mpz_sgn (a)) return v && eval_int_expr (r, CondExpr_thn(e));
    else             return v && eval_int_expr (r, CondExpr_els(e));
  case CastExpr_kind:
    t = expression_type (e); /* Result type. */
    e = CastExpr_expr (e);
    t2 = expression_type (e); /* Operand type. */
    if (!is_arithmetic_type (t) || !is_arithmetic_type (t2)) break;
    if (is_integral_type (t)) {
      v = eval_int_expr (r, e);
      int_type_adjust (r, t);
      return v;
    }
    /* floating type */
    if (expression_tag (e) != FloatExpr_kind || !FloatExpr_val (e)) break;
#warning TODO: remove the || !FloatExpr_val (e) part of the condition
    floating_cast (r, *FloatExpr_val (e), t);
    return TRUE;
  case SizeofExpr_kind:
    mpz_set_ui (r, sizeof_type (expression_type (SizeofExpr_expr (e)), &v));
    return v;
  case SizeofType_kind:
    mpz_set_ui (r, sizeof_type (type_name_type (SizeofType_tname (e)), &v));
    return v;
  case ParExpr_kind:
    return eval_int_expr (r, ParExpr_expr(e));
  case NumExpr_kind:
    mpz_set (r, *NumExpr_val (e));
    return TRUE;
  case CharExpr_kind:
    mpz_set (r, *CharExpr_val (e));
    return TRUE;
  case WideCharExpr_kind:
    mpz_set (r, *WideCharExpr_val (e));
    return TRUE;
  case IdExpr_kind:
    d = IdExpr_decl (e);
    if (!d) break;
    if (declarator_tag (d) != enumerator_kind || !enumerator_v (d)) break;
    mpz_set (r, *enumerator_v (d));
    return TRUE;
  case cfront_comma_expression_kind:
  case IndexExpr_kind:  case CallExpr_kind:
  case MemberExpr_kind: case DerefMemberExpr_kind: case FloatExpr_kind:
  case StringExpr_kind: case LabeledInit_kind:     case CompoundInit_kind:
  case TypeExpr_kind:   case LabelExpr_kind:       case StatExpr_kind:
  case CompoundLit_kind:
    break;
  }
  return FALSE;
}

Bool
EqualIntExprs (expression a, expression b)
{
  mpz_t u, v;

  mpz_init (u);
  mpz_init (v);
  if (!eval_int_expr (u, a)) return FALSE;
  if (!eval_int_expr (v, b)) return FALSE;
  return mpz_cmp (u, v) == 0;
}

AP_Integer
EvalIntExpr (expression e)
{
  mpz_t v;

  mpz_init (v);
  if (!eval_int_expr (v, e)) return NULL;
  return AP_Int (v);
}

AP_Integer
AP_Int(mpz_t v)
{
  AP_Integer r = fe_malloc (sizeof (*r));
  mpz_init_set (*r, v);
  return r;
}
